//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 04/12/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog. If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#include "EdgeDetectorTests.h"

#include "EdgeDetector.h"
#include "Frame.h"
#include "SingleNoteGenerator.h"
#include "ParametricFrameGenerator.h"
#include "TestData.h"
#include "TestRun.h"
#include "FrameGenParamGenMultiplexer.h"
#include "Log.h"
#include "SequenceIO.h"
#include "ReplayFrameGenerator.h"
#include "Rememberer.h"
#include <iterator>

using namespace std;


class SingleEdgeTestSpec : public ITestSpec<EdgeDetector::Event>
{
private:
    const float _freq;
    const float _start_time;
public:    
    SingleEdgeTestSpec(float freq, float start_time)
    : _freq(freq), _start_time(start_time) {}
    
    virtual Detector* createSubject(IEventListener<EdgeDetector::Event>& listener) const
    {
        return new EdgeDetector(listener, 0);
    }
    
    virtual unique_ptr<IIterable<Frame>> createFrames() const
    {
        SingleNoteGenerator* paramGen = new SingleNoteGenerator(_freq, _start_time);
        return unique_ptr<IIterable<Frame>>(
                    new ParametricFrameGenerator(paramGen, 44100.f, 2048));
    }
    
    virtual EventCollection<EdgeDetector::Event> expectedEvents() const
    {
        EventCollection<EdgeDetector::Event> events;
        events.add(EdgeDetector::Event(1.f));
        return events;
    }
};


class DoubleEdgeTestSpec : public ITestSpec<EdgeDetector::Event>
{
private:
    const float _freq_1;
    const float _freq_2;
    const float _start_time_1;
    const float _start_time_2;
public:    
    DoubleEdgeTestSpec(float freq_1, float freq_2, float start_time_1, float start_time_2)
    : _freq_1(freq_1), _freq_2(freq_2),
      _start_time_1(start_time_1), _start_time_2(start_time_2)
    {}
    
    virtual Detector* createSubject(IEventListener<EdgeDetector::Event>& listener) const
    {
        return new EdgeDetector(listener, 0);
    }
    
    virtual unique_ptr<IIterable<Frame>> createFrames() const
    {
        vector<shared_ptr<IFrameGenParamGen> > children;
        children.push_back(shared_ptr<IFrameGenParamGen>(
                new SingleNoteGenerator(_freq_1, _start_time_1)));
        children.push_back(shared_ptr<IFrameGenParamGen>(
                new SingleNoteGenerator(_freq_2, _start_time_2)));
    
        FrameGenParamGenMultiplexer * gen = new FrameGenParamGenMultiplexer(children);
    
        return unique_ptr<IIterable<Frame>>(
            new ParametricFrameGenerator(gen, 44100.f, 2048));
    }
    
    virtual EventCollection<EdgeDetector::Event> expectedEvents() const
    {
        EventCollection<EdgeDetector::Event> events;
        events.add(EdgeDetector::Event(1.f));
        events.add(EdgeDetector::Event(2.f));
        return events;
    }
};


template <typename TDetector>
class MultiEdgeTestSpec : public ITestSpec<typename TDetector::Event>
{
private:
    const vector<pair<float,float>> _notes;
    LOG_DECLARE;
protected:
    const vector<pair<float,float>> notes() { return _notes; }
public:    
    MultiEdgeTestSpec(vector<pair<float,float>> frequency_start_times)
    : _notes(frequency_start_times)
    {
        LOG_INFO(this << " frequency_start_times.size = " << frequency_start_times.size());
        LOG_INFO(this << " _notes.size = " << _notes.size());
    }
    
    virtual Detector* createSubject(IEventListener<typename TDetector::Event>& listener) const
    {
        return new TDetector(listener, std::unique_ptr<std::ostream>(new std::ofstream(
            "/Users/philip/tmp/EdgeDetector_debug")));
    }
    
    virtual unique_ptr<IIterable<Frame>> createFrames() const
    {
        vector<shared_ptr<IFrameGenParamGen> > children;
        for(auto &it : _notes)
        {
            const float& f = it.first;
            const float& t = it.second;
            children.push_back(std::shared_ptr<IFrameGenParamGen>(
                new SingleNoteGenerator(f, t)));
        }
    
        FrameGenParamGenMultiplexer * gen = new FrameGenParamGenMultiplexer(children);
    
        return unique_ptr<IIterable<Frame>>(
            new ParametricFrameGenerator(gen, 44100.f, 2048));
    }
    
    virtual EventCollection<EdgeDetector::Event> expectedEvents() const
    {
        EventCollection<EdgeDetector::Event> events;
        for(auto &it : _notes)
        {
            const float t = it.second;
            events.add(EdgeDetector::Event(t));
        }
        LOG_INFO(this << " expectedEvents() _notes.size = " << _notes.size());
        LOG_INFO(this << " expectedEvents() events.size = " << events.size());
        return events;
    }
};


template <typename TDetector>
class ReplayTestSpec : public ITestSpec<typename TDetector::Event>
{
private:
    const std::vector<short> _time_domain;
    const std::vector<typename TDetector::Event> _expected;
    const std::string _file_path;
    //const unique_ptr<IIterable<Frame>> _creator;
    LOG_DECLARE;
public:
    ReplayTestSpec(const char* file_path, const std::vector<typename TDetector::Event>& expected)
    : _time_domain(SequenceIO::readToVector<short>(file_path)),
      _file_path(file_path),
      _expected(expected)
    {}
    
    virtual Detector* createSubject(IEventListener<typename TDetector::Event>& listener) const
    {
        return new TDetector(listener, std::unique_ptr<std::ostream>(new std::ofstream(
            "/Users/philip/tmp/EdgeDetector_debug")));
    }
    
    virtual std::unique_ptr<IIterable<Frame>> createFrames() const
    {
        return std::unique_ptr<IIterable<Frame>>(
            new ReplayFrameGenerator(_file_path.c_str(), 22050.f, 2048));
    }
    
    virtual EventCollection<EdgeDetector::Event> expectedEvents() const
    {
        EventCollection<EdgeDetector::Event> events;
        for(auto& e : _expected)
        {
            events.add(e);
        }
        return events;
    }
};


template <typename TDetector>
LOG_DEFINE(MultiEdgeTestSpec<TDetector>);


template <typename TDetector>
class ScaleTestSpec: public MultiEdgeTestSpec<TDetector>
{    
public:
    ScaleTestSpec(float start_freq, bool upwards)
    : MultiEdgeTestSpec<TDetector>(BaseClassConstructorArgs(start_freq, upwards))
    {
        std::cout << "ScaleTestSpec::ScaleTestSpec() has " 
                  << this->notes().size() << " notes" << endl;
    }

private: 
    static vector<pair<float,float>> BaseClassConstructorArgs(float start_freq, bool upwards)
    {
        const float interval_ratio = powf(2.f, (upwards?1.f:-1.f) * 1.f/12.f);
        vector<pair<float,float>> args;
        for(int i=0; i<=12; ++i)
        {
            const float f = start_freq * powf(interval_ratio, i);
            const float t = i/4.f + 1.f;
            args.push_back(pair<float,float>(f, t));
        }
        return args;
    }
};


static std::map<float, float> getFastChromaticNotes()
{
    std::map<float, float> notes;
    notes[1.30] = 261.62; // C
    notes[1.50] = 277.18; // C#
    notes[1.75] = 293.66; // D
    notes[2.00] = 311.12; // D# (not from Jane)
    notes[2.20] = 329.62; // E
    notes[2.40] = 349.22; // F
    notes[2.60] = 369.99; // F#
    notes[2.90] = 391.99; // G
    notes[3.10] = 415.30; // G#
    notes[3.40] = 440.00; // A
    notes[3.60] = 466.16; // A# (main)
    notes[3.85] = 493.88; // B
    notes[4.10] = 523.25; // C
    notes[4.30] = 554.36; // C#
    notes[4.60] = 587.32; // D
    notes[4.80] = 622.25; // D#
    notes[5.00] = 659.25; // E
    notes[5.30] = 698.45; // F
    notes[5.50] = 739.98; // F#
    return notes;
}


@implementation EdgeDetectorTests

- (void)testSingleMiddleA
{
    SingleEdgeTestSpec test_spec(440.f, 1.f);
    TestRun<EdgeDetector::Event> runner(test_spec);
    STAssertTrue(runner.execute(), @"SingleMiddleA");
}

- (void)testSingleMiddleC
{
    SingleEdgeTestSpec test_spec(261.625f, 1.f);
    TestRun<EdgeDetector::Event> runner(test_spec);
    STAssertTrue(runner.execute(), @"SingleMiddleC");
}

- (void)testSingleAAndC
{
    DoubleEdgeTestSpec test_spec(440.f, 261.625f, 1.f, 2.f);
    TestRun<EdgeDetector::Event> runner(test_spec);
    STAssertTrue(runner.execute(), @"AAndC");
}

- (void)testScaleUp
{
    ScaleTestSpec<EdgeDetector> test_spec(261.625f, true);
    TestRun<EdgeDetector::Event> runner(test_spec);
    STAssertTrue(runner.execute(), @"UpwardsScale");
}

- (void)testScaleDn
{
    ScaleTestSpec<EdgeDetector> test_spec(261.625f, false);
    TestRun<EdgeDetector::Event> runner(test_spec);
    STAssertTrue(runner.execute(), @"DownwardsScale");
}

- (void)testFastChromatic
{
    const std::map<float, float> notes = getFastChromaticNotes();
    std::vector<EdgeDetector::Event> expected;
    std::transform(notes.begin(), notes.end(),
                   std::inserter(expected, expected.begin()),
                   [](std::pair<float, float> entry){
                        return EdgeDetector::Event(entry.first);
                   });
    ReplayTestSpec<EdgeDetector> test_spec(
        "/Users/philip/dev/jane/note-recog/note-recog/note-recog/testdata/logged_fast_chromatic_24",
        expected);
    TestRun<EdgeDetector::Event> runner(test_spec);
    STAssertTrue(runner.execute(), @"UpwardsScale");
}

@end